package com.zone.infrastructure.persistent.repository;

import cn.hutool.core.bean.BeanUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.zone.domain.strategy.model.entity.StrategyAwardEntity;
import com.zone.domain.strategy.model.entity.StrategyEntity;
import com.zone.domain.strategy.model.entity.StrategyRuleEntity;
import com.zone.domain.strategy.model.valobj.*;
import com.zone.domain.strategy.repository.IStrategyRepository;
import com.zone.infrastructure.persistent.dao.*;
import com.zone.infrastructure.persistent.po.RuleTree;
import com.zone.infrastructure.persistent.po.Strategy;
import com.zone.infrastructure.persistent.po.StrategyAward;
import com.zone.infrastructure.persistent.po.StrategyRule;
import com.zone.infrastructure.persistent.redis.IRedisService;
import com.zone.types.common.Constants;
import com.zone.types.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author: zongzi
 * @description: TODO
 * @date: 2024/5/26
 */
@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {
    @Autowired
    private IStrategyAwardDao strategyAwardDao;
    @Autowired
    private IStrategyDao strategyDao;
    @Autowired
    private IStrategyRuleDao strategyRuleDao;
    @Autowired
    private IAwardDao awardDao;
    @Autowired
    private IRuleTreeDao ruleTreeDao;
    @Autowired
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Autowired
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
    @Autowired
    private IRedisService redisService;
    @Autowired
    private IRaffleActivityDao raffleActivityDao;
    @Autowired
    private IRaffleActivityAccountDayDao raffleActivityAccountDayDao;

    @Override
    public List<StrategyAwardEntity> listStrategyAward(Long strategyId) {
        // 缓存存在策略明细直接返回
        String cacheKey = Constants.RedisKey.generate(Constants.RedisKey.STRATEGY_AWARD_KEY, strategyId);
        List<StrategyAwardEntity> cacheStrategyAwardEntityList = redisService.getValue(cacheKey);
        if (CollectionUtils.isNotEmpty(cacheStrategyAwardEntityList)) {
            return cacheStrategyAwardEntityList;
        }
        // 未查询到策略明细返回空集合
        List<StrategyAward> strategyAwards = strategyAwardDao.listByStrategyId(strategyId);
        if (CollectionUtils.isEmpty(strategyAwards)) {
            return Lists.newArrayList();
        }
        List<StrategyAwardEntity> strategyAwardEntityList = BeanUtil.copyToList(strategyAwards, StrategyAwardEntity.class);
        // 将查询的策略明细放入缓存后，返回结果
        redisService.setValue(cacheKey, strategyAwardEntityList);
        return strategyAwardEntityList;
    }

    @Override
    public StrategyAwardEntity getStrategyAward(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = strategyAwardDao.getStrategyAward(strategyId, awardId);
        return BeanUtil.copyProperties(strategyAward, StrategyAwardEntity.class);
    }

    @Override
    public void storeStrategyAwardTable(String key, Map<Range<Integer>, Integer> awardRangeTable) {
        // 操作的是代理对象，不需要在调用set方法
        String cacheKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_RANGE_TABLE_KEY, key);
        Map<Range<Integer>, Integer> cacheMap = redisService.getMap(cacheKey);
        cacheMap.putAll(awardRangeTable);

        String rangeKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_RATE_RANGE_KEY, key);
        int rateRange = awardRangeTable.keySet().stream()
                .mapToInt(Range::upperEndpoint)
                .max()
                .orElse(0);
        redisService.setValue(rangeKey, rateRange);
    }

    @Override
    public Integer getRateRange(String key) {
        String rangeKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_RATE_RANGE_KEY, key);
        return redisService.getValue(rangeKey);
    }

    @Override
    public Map<Range<Integer>, Integer> getAwardRangeTable(String key) {
        String cacheKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_RANGE_TABLE_KEY, key);
        return redisService.getMap(cacheKey);
    }

    @Override
    public StrategyEntity getStrategyEntity(Long strategyId) {
        Strategy strategy = strategyDao.getStrategy(strategyId);
        return BeanUtil.copyProperties(strategy, StrategyEntity.class);
    }

    @Override
    public List<StrategyRuleEntity> queryStrategyRules(Long strategyId, String ruleModel) {
        List<StrategyRule> strategyRuleList = strategyRuleDao.listStrategyRule(strategyId, ruleModel);
        return BeanUtil.copyToList(strategyRuleList, StrategyRuleEntity.class);
    }

    @Override
    public RuleTreeVO getRuleTreeVoByTreeId(String treeId) {
        RuleTree ruleTree = ruleTreeDao.get(treeId);
        if (Objects.isNull(ruleTree)) {
            return null;
        }
        // 构建规则树
        RuleTreeVO ruleTreeVO = BeanUtil.copyProperties(ruleTree, RuleTreeVO.class);
        // 树根节点key
        ruleTreeVO.setTreeRootNodeKey(ruleTree.getTreeNodeRuleKey());

        // 构建树节点
        Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineMap = ruleTreeNodeLineDao.list(treeId).stream()
                .map(ruleTreeNodeLine -> BeanUtil.copyProperties(ruleTreeNodeLine, RuleTreeNodeLineVO.class))
                .collect(Collectors.groupingBy(RuleTreeNodeLineVO::getRuleNodeFrom));
        Map<String, RuleTreeNodeVO> ruleTreeNodeVOMap = ruleTreeNodeDao.list(treeId).stream()
                .map(ruleTreeNode -> {
                    RuleTreeNodeVO ruleTreeNodeVO = BeanUtil.copyProperties(ruleTreeNode, RuleTreeNodeVO.class);
                    List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList = ruleTreeNodeLineMap.getOrDefault(ruleTreeNode.getRuleKey(), Lists.newArrayList());
                    ruleTreeNodeVO.setRuleTreeNodeLineList(ruleTreeNodeLineVOList);
                    return ruleTreeNodeVO;
                }).collect(Collectors.toMap(RuleTreeNodeVO::getRuleKey, Function.identity()));

        ruleTreeVO.setTreeNodeMap(ruleTreeNodeVOMap);
        return ruleTreeVO;
    }

    @Override
    public void assembleStockData(Long strategyId, Integer awardId, Integer awardCount) {
        // 奖品库存预热，只预热一次
        String stockCacheKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_STOCK_KEY, strategyId, awardId);
        redisService.setAtomicLong(stockCacheKey, awardCount);
    }

    @Override
    public void decrStrategyAwardCountSurplus(Long strategyId, Integer awardId) {
        strategyAwardDao.awardStockDecr(strategyId, awardId);
    }

    @Override
    public boolean descAwardStock(Long strategyId, Integer awardId) {
        String stockCacheKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_STOCK_KEY, strategyId, awardId);
        long decrAfterCount = redisService.decr(stockCacheKey);
        // 已无库存扣减，则扣减库存失败
        if (decrAfterCount < 0) {
            redisService.setAtomicLong(stockCacheKey, 0);
            return false;
        }
        // 库存扣减成功，key不存在则锁该库存
        String stockDescCacheKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_STOCK_COUNT_KEY, strategyId, awardId, decrAfterCount);
        return redisService.setNx(stockDescCacheKey, "lock");
    }

    @Override
    public AwardStockSubTaskVO pollAwardStockSubTask() {
        // 队列无元素返回空
        String awardStockSubQueueKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_STOCK_QUEUE_KEY);
        RBlockingQueue<AwardStockSubTaskVO> blockingQueue = redisService.getBlockingQueue(awardStockSubQueueKey);
        return blockingQueue.poll();
    }

    @Override
    public void offerAwardStockSubTask(AwardStockSubTaskVO subTask) {
        // 间隔3秒在将扣减库存任务加入到队列中
        String awardStockSubQueueKey = Constants.RedisKey.generate(Constants.RedisKey.AWARD_STOCK_QUEUE_KEY);
        RBlockingQueue<AwardStockSubTaskVO> blockingQueue = redisService.getBlockingQueue(awardStockSubQueueKey);
        RDelayedQueue<AwardStockSubTaskVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        delayedQueue.offer(subTask, 3, TimeUnit.SECONDS);
    }

    @Override
    public StrategyRuleEntity queryStrategyAwardRule(Long strategyId, Integer awardId) {
        StrategyRule strategyRule = strategyRuleDao.queryRule(strategyId, awardId);
        return StrategyRule.convert2Entity(strategyRule);
    }

    @Override
    public StrategyRuleEntity queryStrategyRule(Long strategyId, RuleEnums.RuleTypeEnum ruleTypeEnum, RuleEnums.RuleModelEnum ruleModelEnum) {
        StrategyRule strategyRule = strategyRuleDao.queryStrategyRule(strategyId, ruleTypeEnum.getCode(), ruleModelEnum.getCode());
        return StrategyRule.convert2Entity(strategyRule);
    }
}
